有沒有注意過 JavaScript 裡一個神奇的現象?
比如以下程式:
console.log(x);
執行結果:
Uncaught ReferenceError: x is not defined
(Source: 白爛貓貼圖)
因為使用了根本不存在的變數 x
,執行時拋出錯誤,非常合理。
為了彌補,我們趕快增加 x
的變數宣告:
console.log(x);
var x = "OneJar";
你程式老師在你後面,他非常火。
(Source: Youtube)
變數宣告當然是要在使用之前,補在後面有什麼用,一樣會先執行到拋錯的那一行。
執行結果:
undefined
(Source: 白爛貓貼圖)
Why?為什麼宣告在使用之後不會拋錯?而且印了個莫名的答案。
我知道了。
(Source: 網路)
喂,冷靜。
程式不會變魔術,一定有理可循。
其實背後的原因,就是今天文章要介紹的 Hoisting。
Hoisting 這個術語在 MDN 裡翻譯為「提升」,但我覺得這個名詞太抽象,概念上不易理解。
我個人偏好翻成「宣告置頂」。
就像生活中網路論壇常見的置頂文章,當一些文章被設定為置頂文,無論你進入討論版的下一步想做什麼,這些置頂文都會優先被看到。
Hoisting 的效果非常類似這樣的概念。
以下是 W3Schools 裡的介紹:
Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope.
Hoisting 是 JavaScript 的預設行為,把所有宣告效果提到當前 Scope 的頂端。
也就是說,在正式執行程式之前,JavaScript 會先偷跑一個動作——把程式碼中宣告的部分提前到所屬 Scope 的頂端。
var
關鍵字)例如文章開頭舉的例子:
console.log(x);
var x = "OneJar";
運作上等同於:
var x;
console.log(x);
x = "OneJar";
這就是為什麼 JavaScript 變數可以在宣告之前就使用,而不會拋錯。
但我的程式碼明明是 var x = "OneJar";
,宣告同時就給予初始值,為何印出來的結果是 undefined
而非 "OneJar"
?
再引用 W3Schools 裡的原文:
JavaScript Initializations are Not Hoisted.
也就是說,只有宣告的部分會被提升。
這個觀念不那麼直觀,因為不是以一整行的程式碼來看,而是單獨抽出了程式碼中「宣告」的部分。
以 var x = "OneJar";
這行程式來說,裡面包含 2 個動作:
var x
:宣告一個變數名叫 x
。x = "OneJar"
:將賦值給變數 x
(撰寫時將宣告和賦值寫在同一行,這個動作也稱為「初始化」)。Hoisting 的效果只涵蓋動作 1,不涵蓋動作 2。
所以這段程式:
console.log(x);
var x = "OneJar";
經過 Hoisting 後等同於:
var x;
console.log(x);
x = "OneJar";
而非:
var x = "OneJar";
console.log(x);
function
關鍵字)以下這種寫法一定不陌生:
sayHi();
function sayHi(){
console.log('Hi');
}
執行結果:
Hi
是否曾經覺得奇怪,為什麼函數 sayHi()
宣告定義在後面,卻可以提前呼叫?
這也是 Hoisting 效果。
使用 function
關鍵字去宣告函數,整個函數定義都會被提到 Scope 最前面。
function sayHi(){
console.log('Hi');
}
sayHi();
定義函數除了直接使用 function
做宣告和定義,也允許用「var
變數宣告 + function
函數定義」的寫法。
但需要注意的是,就像前面提到:Hoisting 效果只有「宣告」的部分,不包含「初始化」。
例如以下例子:
console.log( sayHi );
console.log( sayHi() );
var sayHi = function(){
return "Hi";
};
執行結果:
undefined
Uncaught TypeError: sayHi is not a function
被提升的只有「var
變數宣告」的部分,「function
函數定義」的部分仍在原本的位置。
運作上的效果就像以下程式碼:
var sayHi;
console.log( sayHi );
console.log( sayHi() );
sayHi = function(){
return "Hi";
};
let
或 const
宣告的變數不具備 Hoisting 效果W3Schools:
Variables and constants declared with let or const are not hoisted!
Day8 文章介紹到 ES6 導入新的變數宣告關鍵字:let
和 const
。
需要注意到,這兩個關鍵字所宣告的變數不會有 Hoisting 效果。
console.log(x);
let x = "OneJar";
執行結果:
Uncaught ReferenceError: x is not defined
let
和 const
其實也有 Hoisting感謝邦友 Caesar 提供一篇文章——我知道你懂 hoisting,可是你了解到多深?,才了解其實 let
和 const
有 hoisting,只是行為不一樣。
例如以下範例:
let x = "OneJar";
function test(){
console.log(x);
let x;
}
test();
如果沒有 hoisting,理論上應該會根據 Scope Chain 找到外面的 x
,印出 "OneJar"
。
但實際上的執行結果:
Uncaught ReferenceError: x is not defined
原因簡單來說,節錄文章的一句話:
let 與 const 也有 hoisting 但沒有初始化為 undefined,而且在賦值之前試圖取值會發生錯誤。
文章作者花了很多篇幅講解 hoisting 背後的運作原理,方知小小的 hoisting 觀念要深入,細節也是無窮無盡。
Hoisting 效果包含:
var
的變數宣告。function
宣告的函數與其定義。Hoisting 效果不包含:
var
宣告的函數定義。let
或 const
的變數宣告。var
不一樣)使用 let 或 const 宣告的變數不具備 Hoisting 效果
最近看到這篇
或許有不同的見解,給你參考
https://blog.techbridge.cc/2018/11/10/javascript-hoisting/?fbclid=IwAR1bZBRYodHNbq3Xo22HsnRoR-uiJEfnXFJmtdR1fKGAutaTqh8FOCQmONo
原來是 huli 大的文章!拜讀之後實在太感嘆,一個看似小小的 hoisting 觀念,真要深入探究水深得很,精通二字談何容易。
感謝 Caesar 大分享~ 已經將文章做了修訂,免得誤人子弟。
若還有發現其他錯誤請不吝指正!